package com.chh.nonstandardjson;

import java.io.IOException;
import java.io.Reader;

public class JsonLexer {

    private final JsonStringReader reader;
    private int currentChar;
    private int line = 1;
    private int column = 0;

    public JsonLexer(JsonStringReader reader) throws IOException {
        this.reader = reader;
        // 初始化第一个字符
        advance();
    }

    /**
     * 读取并返回下一个Token
     * @return 解析出的Token
     * @throws IOException 如果发生I/O错误或语法错误
     */
    public Token nextToken() throws IOException {
        while (currentChar != -1) {
            // 记录Token开始的位置
            int startLine = line;
            int startColumn = column;

            if (isWhitespace(currentChar)) {
                skipWhitespace();
                continue;
            }

            switch (currentChar) {
                case '{':
                    advance();
                    return new Token(TokenType.BEGIN_OBJECT, "{", startLine, startColumn);
                case '}':
                    advance();
                    return new Token(TokenType.END_OBJECT, "}", startLine, startColumn);
                case '[':
                    advance();
                    return new Token(TokenType.BEGIN_ARRAY, "[", startLine, startColumn);
                case ']':
                    advance();
                    return new Token(TokenType.END_ARRAY, "]", startLine, startColumn);
                case ':':
                    advance();
                    return new Token(TokenType.COLON, ":", startLine, startColumn);
                case ',':
                    advance();
                    return new Token(TokenType.COMMA, ",", startLine, startColumn);
                case '"':
                    return readString(startLine, startColumn);
                case 't':
                    return readKeyword("true", TokenType.BOOLEAN, true, startLine, startColumn);
                case 'f':
                    return readKeyword("false", TokenType.BOOLEAN, false, startLine, startColumn);
                case 'n':
                    return readKeyword("null", TokenType.NULL, null, startLine, startColumn);
                default:
                    if (isDigit(currentChar) || currentChar == '-') {
                        return readNumber(startLine, startColumn);
                    }
                    throw new IOException(String.format("不支持该字符 '%c' line %d, column %d", (char) currentChar, line, column));
            }
        }
        return new Token(TokenType.EOF, null, line, column);
    }

    private void advance() {
        int read = reader.read();
        if (read != -1) {
            currentChar = read;
            if (currentChar == '\n') {
                line++;
                column = 0;
            } else {
                column++;
            }
        } else {
            currentChar = -1; // EOF
        }
    }

    private void skipWhitespace() {
        while (currentChar != -1 && isWhitespace(currentChar)) {
            advance();
        }
    }

    private Token readString(int startLine, int startColumn) throws IOException {
        StringBuilder sb = new StringBuilder();
        advance(); // 跳过起始的 "

        while (currentChar != '"') {
            if (currentChar == -1) {
                throw new IOException("字符串格式错误 line " + startLine + ", column " + startColumn);
            }

            if (currentChar == '\\') {
                advance(); // 跳过 \
                switch (currentChar) {
                    case '"': sb.append('"'); break;
                    case '\\': sb.append('\\'); break;
                    case '/': sb.append('/'); break;
                    case 'b': sb.append('\b'); break;
                    case 'f': sb.append('\f'); break;
                    case 'n': sb.append('\n'); break;
                    case 'r': sb.append('\r'); break;
                    case 't': sb.append('\t'); break;
                    case 'u': // 处理Unicode转义, e.g., \\uXXXX
                        sb.append((char) Integer.parseInt(readHex(4), 16));
                        // readHex已经包含了advance，所以这里不需要
                        continue; // 直接开始下一次循环
                    default:
                        throw new IOException("转义错误: \\" + (char)currentChar);
                }
            } else {
                sb.append((char) currentChar);
            }
            advance();
        }
        advance(); // 跳过结束的 "
        return new Token(TokenType.STRING, sb.toString(), startLine, startColumn);
    }

    private String readHex(int count) throws IOException {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            advance();
            if (!isHexDigit(currentChar)) {
                throw new IOException("读取16进制字符错误");
            }
            sb.append((char)currentChar);
        }
        return sb.toString();
    }

    private Token readNumber(int startLine, int startColumn) throws IOException {
        StringBuilder sb = new StringBuilder();
        boolean isFloat = false;

        // 处理负号
        if (currentChar == '-') {
            sb.append((char)currentChar);
            advance();
        }

        // 处理整数部分
        while (currentChar != -1 && isDigit(currentChar)) {
            sb.append((char)currentChar);
            advance();
        }

        // 处理小数部分
        if (currentChar == '.') {
            isFloat = true;
            sb.append((char)currentChar);
            advance();
            if (!isDigit(currentChar)) throw new IOException("浮点数格式错误");
            while (currentChar != -1 && isDigit(currentChar)) {
                sb.append((char)currentChar);
                advance();
            }
        }

        // 处理科学计数法部分
        if (currentChar == 'e' || currentChar == 'E') {
            isFloat = true;
            sb.append((char)currentChar);
            advance();
            if (currentChar == '+' || currentChar == '-') {
                sb.append((char)currentChar);
                advance();
            }
            if (!isDigit(currentChar)) throw new IOException("浮点数格式错误");
            while (currentChar != -1 && isDigit(currentChar)) {
                sb.append((char)currentChar);
                advance();
            }
        }

        String numberStr = sb.toString();
        if (isFloat) {
            return new Token(TokenType.FLOAT, Double.valueOf(numberStr), startLine, startColumn);
        } else {
            return new Token(TokenType.INTEGER, Long.valueOf(numberStr), startLine, startColumn);
        }
    }

    private Token readKeyword(String expected, TokenType type, Object value, int startLine, int startColumn) throws IOException {
        for (char c : expected.toCharArray()) {
            if (currentChar != c) {
                throw new IOException(String.format("不支持该字符 '%c', 关键字 '%s' line %d, column %d",
                        (char) currentChar, expected, line, column));
            }
            advance();
        }
        // 确保关键字后是分隔符
        if (currentChar != -1 && !isSeparator(currentChar)) {
            throw new IOException(String.format("分隔符错误 '%s' line %d, column %d",
                    expected, line, column));
        }
        return new Token(type, value, startLine, startColumn);
    }

    private boolean isWhitespace(int c) {
        return c == ' ' || c == '\t' || c == '\n' || c == '\r';
    }

    private boolean isDigit(int c) {
        return c >= '0' && c <= '9';
    }

    private boolean isHexDigit(int c) {
        return isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
    }

    private boolean isSeparator(int c) {
        return isWhitespace(c) || c == ',' || c == '}' || c == ']';
    }
}